package com.bdqn.qnnews.jwt.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.bdqn.qnnews.jwt.pojo.TokenPayLoad;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.Date;
import java.util.UUID;

/**
 * @author xlzhang
 */
public class JwtTokenUtil {
/**
 * 使用RSA算法生成token
 * @param data 用户数据
 * @param expSecond 超时时间 毫秒
 * @return token
 * @throws JOSEException 异常
 */
public static String generateTokenByRsa(String data, int expSecond) throws JOSEException {
    //创建JWS头，设置签名算法和类型
    JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
            .type(JOSEObjectType.JWT)
            .build();

    //将负载信息封装到Payload中
    TokenPayLoad token = getDefaultPayload(data,expSecond);
    // 将自定义的负载对象转换为 工具的 PayLoad
    Payload payload = new Payload(JSONUtil.toJsonStr(token));
    //创建JWS对象
    JWSObject jwsObject = new JWSObject(jwsHeader, payload);

    //创建RSA签名器，密钥至少是2048位
    RSAKey rsaKey = getDefaultRsaKey();
    JWSSigner jwsSigner = new RSASSASigner(rsaKey);
    //签名
    jwsObject.sign(jwsSigner);
    return jwsObject.serialize();
}

/**
 * 使用RSA算法校验 token
 */
public static TokenPayLoad verifyTokenByRsa(String token) throws ParseException, JOSEException {
    //从token中解析JWS对象
    JWSObject jwsObject = JWSObject.parse(token);
    //使用RSA公钥创建RSA验证器
    RSAKey rsaKey = getDefaultRsaKey();
    RSAKey publicRsaKey = rsaKey.toPublicJWK();
    JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);
    if (!jwsObject.verify(jwsVerifier)) {
        throw new RuntimeException("TOKEN签名不合法，请重新登录！");
    }

    String payload = jwsObject.getPayload().toString();
    TokenPayLoad tokenPayLoad = JSONUtil.toBean(payload, TokenPayLoad.class);
    // 判断超期
    if (tokenPayLoad.getExp() < System.currentTimeMillis()) {
        throw new RuntimeException("TOKEN已过期，请重新登录！");
    }
    return tokenPayLoad;
}

/**
 * 获取载荷数据对象
 * @param data 用户数据
 * @param expSecond 超时时间 毫秒
 * @return
 */
private static TokenPayLoad getDefaultPayload(String data, int expSecond) {
    // 当前时间
    Date now = new Date();
    // 使用 hutool三方工具的日期工具类获取超期时间 时长由参数决定
    Date exp = DateUtil.offsetSecond(now, expSecond);
    return TokenPayLoad.builder()
            // 用户数据(json格式)
            .sub(data)
            // 签发时间
            .iat(now.getTime())
            // 超期时间
            .exp(exp.getTime())
            //jwt的唯一身份标识
            .jti(UUID.randomUUID().toString())
            .build();
}

/**
 * 获取默认的 RSAKey(包含公钥和私钥)
 */
private static RSAKey getDefaultRsaKey() {
    // 从classpath下读取证书文件qnnews.jdk，需要给出加密密钥
    KeyStoreKeyFactory keyStoreKeyFactory =
            new KeyStoreKeyFactory(new ClassPathResource("qnnews.jks"), "news123456".toCharArray());
    //使用别名qnnews来获取RSA秘钥对
    KeyPair keyPair = keyStoreKeyFactory.getKeyPair("qnnews", "news123456".toCharArray());
    //获取RSA公钥
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    //获取RSA私钥
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    // 通过公钥和私钥构建一个RSAKey对象
    return new RSAKey.Builder(publicKey).privateKey(privateKey).build();
}
}